//
// GPSMapEdit
// (c) Konstantin Galichsky (kg@geopainting.com), 2002-2007
//
// 'File|Load' command.
//

# include "StdAfx.h"
# include "Load.h"
# include "Types.h"
# include "Menu.h"
# include "Map.h"
# include "Scale.h"
# include "Levels.h"
# include "NewMap.h"
# include "Log.h"
# include "Status.h"
# include "Position.h"
# include "AddMap.h"
# include "RecentFiles.h"
# include "MapView.h"
# include "MiniMap.h"
# include "Tool.h"
# include "Keyboard.h"
# include "Version.h"
# include "Globals.h"

# if defined (_DEBUG)
# define _SYNC_LOAD // Enable one-thread loading in debug build.
# endif

# ifdef WIN32
static
DWORD _EHFilter (void * _ei) {
# ifdef _DEBUG
	return EXCEPTION_CONTINUE_SEARCH;
# else
	if (::IsDebuggerPresent ())
		return EXCEPTION_CONTINUE_SEARCH;

	const EXCEPTION_POINTERS * pe = reinterpret_cast<EXCEPTION_POINTERS *> (_ei);

	const char * strType = NULL;
	switch (pe->ExceptionRecord->ExceptionCode) {
		case EXCEPTION_ACCESS_VIOLATION:    strType = "access violation"; break;
		case EXCEPTION_STACK_OVERFLOW:      strType = "stack overlow"; break;
		case EXCEPTION_FLT_DIVIDE_BY_ZERO:  strType = "divide by zero (floating-point)"; break;
		case EXCEPTION_ILLEGAL_INSTRUCTION: strType = "illegal instruction"; break;
		case EXCEPTION_INT_DIVIDE_BY_ZERO:  strType = "divide by zero (integer)"; break;
			break;

		case EXCEPTION_BREAKPOINT:
		default:
			return EXCEPTION_CONTINUE_SEARCH;
	}

	ReportWarning ("Internal error.");
	ReportText ("Please report the error log to the developer (kg@geopainting.com):");
	ReportText ("------------------------------");
	ReportText ("Instruction at [0x%08x] caused %s.", pe->ExceptionRecord->ExceptionAddress, strType);

	if (
		pe->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION &&
		pe->ExceptionRecord->NumberParameters >= 2
	) {
		ReportText (
			"Attempt to %s to [0x%08x] which is illegal address.",
			pe->ExceptionRecord->ExceptionInformation [0] == 0 ? "read" : "write",
			pe->ExceptionRecord->ExceptionInformation [1]
		);
	}
/*
	// Report stack.
	STACKFRAME sf;
	const size_t cStackDepth = 5;
	DWORD dwPC [cStackDepth];
	for (size_t cFrame = 0; cFrame < cStackDepth; ++ cFrame) {
		::StackWalk (IMAGE_FILE_MACHINE_I386, ::GetCurrentProcess (), ::GetCurrentThread (), & sf, NULL, NULL, NULL, NULL, NULL);
		dwPC [cFrame] = sf.AddrPC.Offset;
	}
	ReportText ("Stack: 0x%08x 0x%08x 0x%08x 0x%08x", dwPC [0], dwPC [1], dwPC [2], dwPC [3]);
*/

	// Report program version.
	ReportText ("GPSMapEdit version: " GME_VER_S);

	// Report OS version.
	OSVERSIONINFO oi = {sizeof (OSVERSIONINFO)};
	::GetVersionEx (& oi);
	ReportText (
		"OS version: %s %d.%d %s",
		oi.dwPlatformId == VER_PLATFORM_WIN32_NT ? "NT" : "9x",
		oi.dwMajorVersion, oi.dwMinorVersion, oi.szCSDVersion 
	);

	ReportText ("------------------------------");

	return EXCEPTION_EXECUTE_HANDLER;
# endif // _DEBUG
}
# endif // WIN32

static
LoadStatus_t _ExecuteLoadMapSafely (map_t * _pMap, const char * _pData, DWORD _dwFileSize, const char * _strFileName, CMapLoader * _pLoader) {
# ifdef WIN32
	__try {
		assert (_pMap);
		return _pMap->LoadMap (_pData, _dwFileSize, _strFileName, _pLoader);
	} __except (_EHFilter (_exception_info ())) {
		return lsFailed;
	}
# else
	return _pMap->LoadMap (_pData, _dwFileSize, _strFileName, _pLoader);
# endif // WIN32
}

# ifdef WIN32
LoadStatus_t CMapLoader::LoadFile (const char * _strFileName, map_t * _pMap, const CFileTypeInfo ** _ppSaverInfo) {
	const CFileForRead hFile (_strFileName);
	if (! hFile.IsValid ()) {
		ReportWarning ("Can't open file '%s'.", _strFileName);
		return lsFailed;
	}

	DWORD dwFileSizeHi = 0;
	DWORD dwFileSize = ::GetFileSize (hFile, & dwFileSizeHi);

	// NOTE: some ECW/J2K files may be over 512MB. For huge files, allow to map only the first 1MB to verify header.
	const DWORD dwHdrSize = dwFileSizeHi > 0 ? 1000000 : __min (dwFileSize, 1000000);

	CWinHandle hFileMapping = ::CreateFileMapping (hFile, NULL, PAGE_READONLY, 0, 0, NULL);
	const char * pData = reinterpret_cast<char *> (::MapViewOfFile (hFileMapping, FILE_MAP_READ, 0, 0, dwHdrSize));
	if (! pData) {
		ReportWarning ("Can't read file '%s'.", _strFileName);
		return lsFailed;
	}

	DWORD dwFlags = 0;
	CMapLoader * const pLoader = CFileTypeInfo::CreateLoader (pData, dwFileSize, _strFileName, _ppSaverInfo, dwFlags);
	if (! pLoader) {
		::UnmapViewOfFile (pData);

		ReportWarning ("Unknown file type.", _strFileName);
		return lsFailed;
	}

	if (dwFlags & ftiDontMapFile) {
		// NOTE: some ECW/J2K files may be huge while their native API does not use this memory-mapped file image at all.
		::UnmapViewOfFile (pData);
		pData = NULL;
	} else {
		if (dwFileSizeHi > 0 || dwFileSize >= 0x80000000) {
			::UnmapViewOfFile (pData);
			ReportWarning ("File '%s' is too huge (> 2GB).", _strFileName);
			return lsFailed;
		}

		if (dwHdrSize != dwFileSize) {
			::UnmapViewOfFile (pData);
			pData = reinterpret_cast<char *> (::MapViewOfFile (hFileMapping, FILE_MAP_READ, 0, 0, dwFileSize));
		}
	}

	LoadStatus_t ls = lsFailed;
	try {
		ls = _ExecuteLoadMapSafely (_pMap, pData, dwFileSize, _strFileName, pLoader);
	} catch (COutOfMemory & _oom) {
		const float fRequestedMB = _oom.RequestedSize ()/(1024.f*1024);
		const float fFreeMB      = _oom.AvailableSize ()/(1024.f*1024);

		ReportWarning ("Not enough free memory.");
		ReportText (" %0.3f MB requested, %0.3f MB free (but may be highly fragmented).", fRequestedMB, fFreeMB);
		if (_oom.WarnSmallPageFile ()) {
			ReportText ("NOTE: OS setting for paging file size is smaller than RAM size.");
			ReportText ("This may greatly limit OS in memory allocation.");
		}
	} catch (const std::exception & _e) {
		ReportText ("Unhandled exception: %s.", _e.what ());
	}

	if (dwFlags & ftiWTR)
		g_bHaveWaypointsOrTracks = true;

	if ((dwFlags & ftiRenderer) == 0 || ls != lsOK)
		delete pLoader;
	else {
		CMapRenderer * const pMapRenderer = static_cast<CMapRenderer *> (pLoader);
		map_renderer_t * const pRenderer = new map_renderer_t (pMapRenderer);
		pRenderer->strFileName = _strFileName;

		_pMap->renderers.push_back (pRenderer);
		_pMap->bNoHeader = true;
		_pMap->rectBound = pRenderer->rect;
		_pMap->fCosY     = ::cos (pRenderer->rect.MeanY ()*c_PI/180.f);
	}

	if (pData)
		::UnmapViewOfFile (pData);

	return ls;
}
# endif // WIN32

///////////////////////////////////////////////

HANDLE g_hLoadThread;
volatile bool g_bLoading = false;
volatile bool g_bStop = false;
file_names_t g_last_file_names;

struct load_data_t {
	map_t *      pMap;
	file_names_t file_names;
	size_t       cFileType;
	bool         bRemoveOldMap;
};

DWORD WINAPI _LoadMapProc (LPVOID);

void LoadMap (const char * _strFileName, size_t _cFileType, bool _bRemoveOldMap, map_t & _map) {
	file_names_t file_names;
	file_names.push_back (_strFileName);

	LoadMap (file_names, _cFileType, _bRemoveOldMap, _map);
}

void LoadMap (const file_names_t & _file_names, size_t _cFileType, bool _bRemoveOldMap, map_t & _map) {
	// Wait for previous parsing ends.
	if (g_hLoadThread)
		StopLoadThread ();

	g_bStop = false;

	g_bLoading = true;
	UpdateMenu ();

	// Simulate removal of map if it is initially empty.
	// NOTE: 'Add' map(s) to empty one should be strongly equivalent of 'Open'.
	if (! _bRemoveOldMap) {
		CCritSec::CLock lock (g_cs);
		if (_map.empty ())
			_bRemoveOldMap = true;
	}

	// Remove old map.
	if (_bRemoveOldMap && & _map == & g_map) {
		const bool bCancelled = ! RemoveMap ();
		if (bCancelled) {
			g_bLoading = false;
			UpdateMenu ();
			return;
		}
	}

	if (& _map == & g_map)
		g_last_file_names.clear ();

	// Prepare input data for the thread proc.
	load_data_t * const pParams = new load_data_t;
	pParams->pMap          = & _map;
	pParams->cFileType     = _cFileType;
	pParams->bRemoveOldMap = _bRemoveOldMap;
	std::set<string_t> names;
	for (file_names_t::const_iterator fi = _file_names.begin (); fi != _file_names.end (); ++ fi) {
		// Make absolute file path.
		char strFullPathName [MAX_PATH + 1];
		char * p = NULL;
		::GetFullPathName (fi->c_str (), MAX_PATH, strFullPathName, & p);

		if (names.find (strFullPathName) != names.end ())
			// Omit duplicated name.
			continue;

		names.insert (strFullPathName);
		pParams->file_names.push_back (strFullPathName);
		if (& _map == & g_map)
			g_last_file_names.push_back (strFullPathName);
	}

# if defined (_SYNC_LOAD)
	_LoadMapProc (pParams);
# else
	// Perform the loading in parallel thread.
	DWORD dwThreadId = 0;
	assert (! g_hLoadThread);
	g_hLoadThread = ::CreateThread (NULL, 0, & _LoadMapProc, pParams, 0, & dwThreadId);
	if (! g_hLoadThread) {
		ErrorMsgBox ("Internal error.");
		return;
	}
	// NOTE: the thread's handle 'g_hLoadThread' is not closed until StopLoadThread() or next load.
# endif
}

static
DWORD WINAPI _LoadMapProc1 (LPVOID _pParams) {
	// Get input params.
	load_data_t * const pParams = reinterpret_cast<load_data_t *> (_pParams);

	map_t & _map = * pParams->pMap;

	load_data_t params;
	params.file_names.swap (pParams->file_names);
	params.pMap          = pParams->pMap;
	params.cFileType     = pParams->cFileType;
	params.bRemoveOldMap = pParams->bRemoveOldMap;
	delete pParams;

	CWaiter w;

	ClearLog ();

	// Save view state.
//	const size_t cOrigLevel = GetVisibleLevel ();
	const float fOrigScale  = g_fScale;
	const float fOrigX      = g_X + _map.rectBound.x0;
	const float fOrigY      = g_Y + _map.rectBound.y0;

	const long lStartTime = ::GetTickCount ();

	bool bAtLeastOneFileIsLoaded = false;
	bool bShowMessageLog = false;
	for (file_names_t::const_iterator fi = params.file_names.begin (); fi != params.file_names.end (); ++ fi) {
		const char * strFileName = fi->c_str ();
		const bool bFirstFile = fi == params.file_names.begin ();

		if (! bFirstFile)
			ReportText ("");
		ReportText ("Loading data from file '%s'...", strFileName);

		g_bStop = false;

		map_t map;
		const CFileTypeInfo * pSaverInfo = NULL;
		const LoadStatus_t ls = CMapLoader::LoadFile (strFileName, & map, & pSaverInfo);
		switch (ls) {
			case lsOK:
				break;

			case lsCanceled:
				ReportText ("Loading was canceled by user request.");
				continue;

			case lsInterrupted:
				ReportText ("Loading was interrupted by user request.");
				bShowMessageLog = true;
				goto l_end_of_load;

			default:
			case lsFailed:
				ReportText ("ERROR: loading has terminated.");
				bShowMessageLog = true;
				goto l_end_of_load;
		}

		if (params.bRemoveOldMap && & _map == & g_map)
			AddRecentFile (strFileName);

		//
		// Actualize the new map.
		//

		if (bFirstFile) {
			if (params.bRemoveOldMap) {
				CCritSec::CLock lock (g_cs);
				_map.swap (map);

				if (& _map == & g_map) {
					::strcpy (g_strFileName, strFileName);
					g_pSaverInfo = pSaverInfo;
				}
			} else {
				if (! CanAddMap (map, _map)) {
					ReportWarning ("Sorry, the map can't be merged with previously loaded data due to different type set.");
					bShowMessageLog = true;
				} else if (! AddMap (map, _map)) {
					if (bAtLeastOneFileIsLoaded)
						goto l_end_of_load;

					ResetStatus ();
					g_bLoading = false;
					UpdateMenu ();

					return 1;
				}
			}
		} else {
			if (CanAddMap (map, _map)) {
				CCritSec::CLock lock (g_cs);
				_map.Join (map);
			} else {
				ReportWarning ("Sorry, the map can't be merged with previously loaded data due to different type set.");
				bShowMessageLog = true;
			}
		}

		bAtLeastOneFileIsLoaded = true;
	}
l_end_of_load:
	g_bStop = false;

	//
	// Report about results.
	//

	// Report warnings count.
	if (GetWarningsCount ())
		ReportText ("%d warning(s).", GetWarningsCount ());

	// Report time of loading.
	ReportText ("Load time is %0.2f s.", .001*(::GetTickCount () - lStartTime));

	// Report user interruption or load failure.
	if (bShowMessageLog) {
		w.Stop ();
		ShowMessageLog ();
	}
	if (! bAtLeastOneFileIsLoaded) {
		ResetStatus ();
		g_bLoading = false;
		UpdateMenu ();

		return 1;
	}

	//
	// Check if map is empty.
	//
	bool bRectBoundInvalid;
	{
		CCritSec::CLock lock (g_cs);
		bRectBoundInvalid = _map.rectBound.Invalid ();
	}
	if (bRectBoundInvalid) {
		ResetStatus ();
		g_bLoading = false;
		UpdateMenu ();

		ReportText ("ERROR: The map is empty.");
		ShowMessageLog ();

		return 1;
	}

	//
	// Set the view to the map's center.
	//

	if (& _map == & g_map) {
		// Make sure that all new loaded data will be visible.
		g_bShowAttachments = true;

		if (g_bZoomToFullAfterAdd || params.bRemoveOldMap) {
			SetVisibleLevelAuto ();
			if (params.bRemoveOldMap) {
				const bool bOK = RestoreRecentMapPosition ();
				if (! bOK)
					SetFullMapScale ();
			} else
				SetFullMapScale ();
		} else {
			// Restore map scale & position.
	//		SetVisibleLevel (cOrigLevel);
			{
				CCritSec::CLock lock (g_cs);

				g_fScale = fOrigScale;
				g_X = fOrigX - _map.rectBound.x0;
				g_Y = fOrigY - _map.rectBound.y0;
			}
			
			RefreshScales ();
			Refresh ();
		}

		//
		// Render the mini-map.
		//
		RenderMiniMap ();
	} else
		Refresh ();

	//
	// Update GUI elements.
	//

	// NOTE: the code below this should not Refresh() to avoid deadlock!
	g_bLoading = false;

	UpdateTitle ();
	MakeLevelsMenu ();
	UpdateMenu ();

	// NOTE: previous Refresh() does not display ruler due to g_bLoading==false.
	// Make ruler to draw now.
	Redraw ();

	// Redraw mini-map.
	RedrawMiniMap ();

	CTool::_OnNewMap ();

	SetStatusReady ();

	return 0;
}

static
DWORD WINAPI _LoadMapProc (LPVOID _pParams) {
	CCom com;

	const DWORD dwRet = _LoadMapProc1 (_pParams);
	::PostMessage (g_hWnd, WM_USER_FILE_LOADED, dwRet, 0);
	g_bLoading = false;

	return dwRet;
}

void WaitLoadThread () {
# if ! defined (_SYNC_LOAD)
	if (g_hLoadThread == NULL)
		return;

	while (true) {
		const DWORD dwRet = ::MsgWaitForMultipleObjects (1, & g_hLoadThread, FALSE, INFINITE, QS_ALLINPUT);
		if (dwRet == WAIT_OBJECT_0)
			// The thread exits.
			break;

		if (dwRet != WAIT_OBJECT_0 + 1)
			// Smth else happened.
			break; 

		// There is one or more window message available. Dispatch them.
		MSG msg;
		while (::PeekMessage (& msg, NULL, 0, 0, PM_REMOVE)) {
			if (! TranslateAccelerator (msg)) {
				::TranslateMessage (& msg);
				::DispatchMessage (& msg);
			}
			if (::WaitForSingleObject (g_hLoadThread, 0) == WAIT_OBJECT_0) {
				// The thread exits now.
				::CloseHandle (g_hLoadThread);
				g_hLoadThread = NULL;
				return;
			}
		}
	}

	::CloseHandle (g_hLoadThread);
	g_hLoadThread = NULL;
# endif // _SYNC_LOAD
}

void StopLoadThread () {
	g_bStop = true;

	WaitLoadThread ();
}
